{
  // 软件工程中，我们不仅要创建一致的定义良好的API，同时也要考虑可重用性。 组件不仅能够支持当前的数据类型，同时也能支持未来的数据类型，这在创建大型系统时为你提供了十分灵活的功能。
  // 在像C#和Java这样的语言中，可以使用泛型来创建可重用的组件，一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
  // 有时函数的返回值类型与参数类型相关
  // 泛型的特点就是带有“类型参数”（type parameter）。

  {
    // 不用泛型
    function identity(arg: any): any {
      return arg;
    }
  }

  {
    // 使用泛型
    function identity<T>(arg: T): T {
      return arg;
    }

    // let output = identity<string>("myString");  // type of output will be 'string'

    // 为了方便，函数调用时，往往省略不写类型参数的值，让 TypeScript 自己推断。
    // 当推断不出类型参数时，就必须显式给出了
    // 常用 类型推论
    let output = identity("myString"); // type of output will be 'string'
  }

  {
    function comb<T>(arr1: T[], arr2: T[]): T[] {
      return arr1.concat(arr2);
    }

    // comb([1, 2], ["a", "b"]); // 报错

    comb<number | string>([1, 2], ["a", "b"]); // 正确
  }

  {
    // 泛型的写法
    // 写在方法名后，尖括号中
    function id<T>(arg: T): T {
      return arg;
    }

    // 变量形式定义函数，两种写法：
    // 写法一
    let myId: <T>(arg: T) => T = id;

    // 写法二
    // let myId: { <T>(arg: T): T } = id;
  }

  {
    // 多个类型参数
    function swap<T, U>(tuple: [T, U]): [U, T] {
      return [tuple[1], tuple[0]];
    }

    swap([7, "seven"]); // ['seven', 7]
  }

  {
    // 泛型约束
    interface Lengthwise {
      length: number;
    }

    function loggingIdentity<T extends Lengthwise>(arg: T): T {
      console.log(arg.length);
      return arg;
    }

    // Argument of type '{ name: number; }' is not assignable to parameter of type 'Lengthwise'.
    // loggingIdentity({ name: 8 });
    loggingIdentity({ length: 8 });
  }

  {
    // 泛型参数的默认类型 泛型类型 = 类型
    function createArray<T = string>(length: number, value: T): Array<T> {
      let result: T[] = [];
      for (let i = 0; i < length; i++) {
        result[i] = value;
      }
      return result;
    }
  }
}
